def _run_tool(tool, extra_args): """Run external executable tool. Args: tool: string name of the tool to run. extra_args: additional arguments for tool. Returns: A tuple of the (stdout, stderr) from the process. Raises: BuildError: if tool fails. """ args = [tool] if sys.platform.startswith('win'): args = [tool + '.exe'] args.extend(extra_args) logging.debug('Calling: %s', ' '.join(args)) try: process = safe_subprocess.start_process(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() except OSError as e: msg = '%s not found.' % args[0] raise go_errors.BuildError('%s\n%s' % (msg, e)) if process.returncode: raise go_errors.BuildError( '(Executed command: %s)\n\n%s\n%s' % (' '.join(args), stdout, stderr)) return stdout, stderr
def maybe_build(self, maybe_modified_since_last_build): """Builds an executable for the application if necessary. Args: maybe_modified_since_last_build: True if any files in the application root or the GOPATH have changed since the last call to maybe_build, False otherwise. This argument is used to decide whether a build is Required or not. Returns: True if compilation was successfully performed (will raise an exception if compilation was attempted but failed). False if compilation was not attempted. Raises: BuildError: if building the executable fails for any reason. """ if not self._work_dir: self._work_dir = tempfile.mkdtemp('appengine-go-bin') atexit.register(_rmtree, self._work_dir) if not os.path.exists(_GAB_PATH): # TODO: This message should be more useful i.e. point the # user to an SDK that does have the right components. raise go_errors.BuildError( 'Required Go components are missing from the SDK.') if self._go_executable and not maybe_modified_since_last_build: return False (self._go_file_to_mtime, old_go_file_to_mtime) = (self._get_go_files_to_mtime(), self._go_file_to_mtime) if not self._go_file_to_mtime: raise go_errors.BuildError( 'no .go files found in %s' % self._module_configuration.application_root) self._extras_hash, old_extras_hash = (self._get_extras_hash(), self._extras_hash) if (self._go_executable and self._go_file_to_mtime == old_go_file_to_mtime and self._extras_hash == old_extras_hash): return False if self._go_file_to_mtime != old_go_file_to_mtime: logging.debug( 'Rebuilding Go application due to source modification') elif self._extras_hash != old_extras_hash: logging.debug( 'Rebuilding Go application due to GOPATH modification') else: logging.debug('Building Go application') self._build() return True
def _run_gab(self, gab_extra_args, env): """Run go-app-builder. Args: gab_extra_args: additional arguments (i.e. other than the standard base arguments) for go-app-builder. env: A dict containing environment variables for the subprocess. Returns: A tuple of the (stdout, stderr) from the go-app-builder process. Raises: BuildError: if the go application builder fails. """ gab_path = os.path.join(self._goroot, 'bin', 'go-app-builder') if sys.platform.startswith('win'): gab_path += '.exe' if not os.path.exists(gab_path): # TODO: This message should be more useful i.e. point the # user to an SDK that does have the right components. raise go_errors.BuildError( 'Required Go components are missing from the SDK.') # Go's regexp package does not implicitly anchor to the start. gab_args = [ gab_path, '-app_base', self._module_configuration.application_root, '-arch', self._arch, '-dynamic', '-goroot', self._goroot, '-gopath', os.environ.get('GOPATH', GOPATH), '-nobuild_files', '^' + str(self._module_configuration.nobuild_files), '-incremental_rebuild', '-unsafe', ] gab_args.extend(gab_extra_args) gab_process = safe_subprocess.start_process(gab_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) gab_stdout, gab_stderr = gab_process.communicate() if gab_process.returncode: raise go_errors.BuildError( '(Executed command: %s)\n%s\n%s' % (' '.join(gab_args), gab_stdout, gab_stderr)) return gab_stdout, gab_stderr
def _run_gab(application_root, nobuild_files, arch, gab_extra_args, env): """Run go-app-builder. Args: application_root: string path to the root dir of the application. nobuild_files: regexp identifying which files to not build. arch: The one-character architecture designator (5, 6, or 8). gab_extra_args: additional arguments (i.e. other than the standard base arguments) for go-app-builder. env: A dict containing environment variables for the subprocess. Returns: A tuple of the (stdout, stderr) from the go-app-builder process. Raises: BuildError: if the go application builder fails. """ gab_args = _get_base_gab_args(application_root, nobuild_files, arch) gab_args.extend(gab_extra_args) gab_process = safe_subprocess.start_process(gab_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) gab_stdout, gab_stderr = gab_process.communicate() if gab_process.returncode: raise go_errors.BuildError( '(Executed command: %s)\n%s\n%s' % (' '.join(gab_args), gab_stdout, gab_stderr)) return gab_stdout, gab_stderr
def _get_pkg_path(): for n in os.listdir(os.path.join(GOROOT, 'pkg')): # Look for 'linux_amd64_appengine', 'windows_386_appengine', etc. if n.endswith('_appengine'): return os.path.join(GOROOT, 'pkg', n) raise go_errors.BuildError('No package path found in goroot (%s)' % GOROOT)
def _get_architecture(goroot): """Get the architecture number for the go compiler. Args: goroot: The string path to goroot. Returns: The architecture number, as a string, for the go compiler. Raises: BuildError: If the arch for the goroot isn't one we support. """ architecture_map = { 'arm': '5', 'amd64': '6', '386': '8', } for platform in os.listdir(os.path.join(goroot, 'pkg', 'tool')): # Look for 'linux_amd64', 'windows_386', etc. if '_' not in platform: continue architecture = platform.split('_', 1)[1] if architecture in architecture_map: return architecture_map[architecture] raise go_errors.BuildError('No known compiler found in goroot (%s)' % goroot)
def _build(self): """Builds the Managed VM app locally. Note that the go compiler must be called from within the app directory. Otherwise, it returns an error like: can't load package: package /a/b: import "/a/b": cannot import absolute path Raises: BuildError: if build fails. """ logging.debug('Building Go application') app_root = self._module_configuration.application_root exe_name = os.path.join(self._work_dir, '_ah_exe') args = ['build', '-tags', 'appenginevm', '-o', exe_name] try: cwd = os.getcwd() os.chdir(app_root) stdout, stderr = _run_tool('go', args) finally: os.chdir(cwd) if not _file_is_executable(exe_name): raise go_errors.BuildError( 'Your Go app must use "package main" and must provide' ' a "func main". See https://cloud.google.com/appengine' '/docs/go/managed-vms/ for more information.') logging.debug('Build succeeded:\n%s\n%s', stdout, stderr) self._go_executable = exe_name
def _get_architecture(): architecture_map = { 'arm': '5', 'amd64': '6', '386': '8', } for platform in os.listdir(os.path.join(GOROOT, 'pkg', 'tool')): # Look for 'linux_amd64', 'windows_386', etc. if '_' not in platform: continue architecture = platform.split('_', 1)[1] if architecture in architecture_map: return architecture_map[architecture] raise go_errors.BuildError('No known compiler found in goroot (%s)' % GOROOT)
def _get_pkg_path(goroot): """The the path to the go pkg dir for appengine. Args: goroot: The path to goroot. Returns: The path to the go appengine pkg dir. Raises: BuildError: If the no package dir was found. """ for n in os.listdir(os.path.join(goroot, 'pkg')): # Look for 'linux_amd64_appengine', 'windows_386_appengine', etc. if n.endswith('_appengine'): return os.path.join(goroot, 'pkg', n) raise go_errors.BuildError('No package path found in goroot (%s)' % goroot)
def maybe_build(self): """Builds an executable for the application if necessary. Returns: True if compilation was successfully performed (will raise an exception if compilation was attempted but failed). False if compilation was not attempted. Raises: BuildError: if building the executable fails for any reason. """ if not self._work_dir: self._work_dir = tempfile.mkdtemp('appengine-go-bin') atexit.register(_rmtree, self._work_dir) if self._go_executable: return False (self._go_file_to_mtime, old_go_file_to_mtime) = (self._get_go_files_to_mtime(), self._go_file_to_mtime) if not self._go_file_to_mtime: raise go_errors.BuildError('no .go files found in %s' % self._module_configuration.application_root) self._extras_hash, old_extras_hash = (self._get_extras_hash(), self._extras_hash) if (self._go_executable and self._go_file_to_mtime == old_go_file_to_mtime and self._extras_hash == old_extras_hash): return False if self._go_file_to_mtime != old_go_file_to_mtime: logging.debug('Rebuilding Go application due to source modification') elif self._extras_hash != old_extras_hash: logging.debug('Rebuilding Go application due to GOPATH modification') else: logging.debug('Building Go application') self._build() return True