pkg/firebase/util/util.go (89 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package util provides utility functions to build applications using the Firebase App Hosting builder.
package util
import (
"errors"
"os"
"path/filepath"
"github.com/GoogleCloudPlatform/buildpacks/pkg/env"
"github.com/GoogleCloudPlatform/buildpacks/pkg/firebase/faherror"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
)
var (
supportedMonorepoConfigFiles = []string{"nx.json"}
)
// ApplicationDirectory looks up the path to the application directory from the environment. Returns
// the application root by default.
func ApplicationDirectory(ctx *gcp.Context) string {
appDir := ctx.ApplicationRoot()
if appDirEnv, exists := os.LookupEnv(env.Buildable); exists {
appDir = filepath.Join(ctx.ApplicationRoot(), appDirEnv)
}
return appDir
}
// supportedMonorepoConfigFileExists checks if a supported monorepo config file exists in the
// specified directory.
func supportedMonorepoConfigFileExists(dir string) (bool, error) {
for _, filename := range supportedMonorepoConfigFiles {
f := filepath.Join(dir, filename)
_, err := os.ReadFile(f)
if os.IsNotExist(err) {
continue
}
if err != nil {
return false, err
}
return true, nil
}
return false, nil
}
// buildDirectoryContext returns (1) the "build directory" from which the buildpacks will be run,
// and (2) the directory containing the application to be built, relative to the build directory.
//
// The build directory and application directory are different in monorepo contexts, in which we
// want to run the buildpacks process from the root of the monorepo to ensure all necessary files
// are accessible, but we want to build the application inside the user-specified subdirectory.
// see go/apphosting-monorepo-support for more details.
func buildDirectoryContext(cwd, userSpecifiedAppDirPath string) (string, string, error) {
if userSpecifiedAppDirPath == "" {
return "", "", nil
}
absoluteAppDirPath := filepath.Join(cwd, userSpecifiedAppDirPath)
_, err := os.Stat(absoluteAppDirPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return "", "", faherror.InvalidRootDirectoryError(userSpecifiedAppDirPath, err)
}
return "", "", err
}
var monorepoRootPath string
curr := absoluteAppDirPath
for {
exists, err := supportedMonorepoConfigFileExists(curr)
if err != nil {
return "", "", err
}
if exists {
monorepoRootPath = curr
break
}
if curr == cwd || curr == "/" || curr == "." {
break
}
curr = filepath.Dir(curr)
}
if monorepoRootPath == "" {
// If no monorepo config file is detected, then the user-specified app directory path is the
// root of an application in a subdirectory.
return userSpecifiedAppDirPath, "", nil
}
// If a monorepo config file is detected, then the monorepo root is the "build directory" and the
// user-specified app directory path is the root of the sub-application.
mrp, err := filepath.Rel(cwd, monorepoRootPath)
if err != nil {
return "", "", err
}
adp, err := filepath.Rel(monorepoRootPath, absoluteAppDirPath)
if err != nil {
return "", "", err
}
return mrp, adp, nil
}
// WriteBuildDirectoryContext writes the build directory context to the specified buildpack config
// file path.
func WriteBuildDirectoryContext(cwd, appDirectoryPath, buildpackConfigOutputFilePath string) error {
buildDirectory, relativeProjectDirectory, err := buildDirectoryContext(cwd, appDirectoryPath)
if err != nil {
return err
}
err = os.MkdirAll(buildpackConfigOutputFilePath, 0755)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(buildpackConfigOutputFilePath, "build-directory.txt"), []byte(buildDirectory), 0644)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(buildpackConfigOutputFilePath, "relative-project-directory.txt"), []byte(relativeProjectDirectory), 0644)
}