blob: 72a1c3faa1141c4bf74b517b83df38400ba8829b [file] [log] [blame]
from __future__ import print_function
import os
import platform
import subprocess
import sys
import tempfile
import textwrap
from timing import monotonic_time_nanos
from tracing import Tracing
from buck_tool import BuckTool, which, check_output
from buck_tool import BuckToolException, RestartBuck
from buck_version import get_clean_buck_version, get_dirty_buck_version
JAVA_CLASSPATHS = [
"build/abi_processor/classes",
"build/classes",
"build/dx_classes",
"src",
"third-party/java/android/sdklib.jar",
"third-party/java/aopalliance/aopalliance.jar",
"third-party/java/args4j/args4j-2.0.30.jar",
"third-party/java/asm/asm-debug-all-5.0.3.jar",
"third-party/java/astyanax/astyanax-cassandra-1.56.38.jar",
"third-party/java/astyanax/astyanax-core-1.56.38.jar",
"third-party/java/astyanax/astyanax-thrift-1.56.38.jar",
"third-party/java/astyanax/cassandra-1.2.3.jar",
"third-party/java/astyanax/cassandra-thrift-1.2.3.jar",
"third-party/java/astyanax/high-scale-lib-1.1.2.jar",
"third-party/java/closure-templates/soy-excluding-deps.jar",
"third-party/java/commons-cli/commons-cli-1.1.jar",
"third-party/java/commons-codec/commons-codec-1.2.jar",
"third-party/java/commons-compress/commons-compress-1.8.1.jar",
"third-party/java/commons-lang/commons-lang-2.6.jar",
"third-party/java/dd-plist/dd-plist.jar",
"third-party/java/ddmlib/ddmlib-22.5.3.jar",
"third-party/java/eclipse/org.eclipse.core.contenttype_3.4.200.v20130326-1255.jar",
"third-party/java/eclipse/org.eclipse.core.jobs_3.5.300.v20130429-1813.jar",
"third-party/java/eclipse/org.eclipse.core.resources_3.8.101.v20130717-0806.jar",
"third-party/java/eclipse/org.eclipse.core.runtime_3.9.100.v20131218-1515.jar",
"third-party/java/eclipse/org.eclipse.equinox.common_3.6.200.v20130402-1505.jar",
"third-party/java/eclipse/org.eclipse.equinox.preferences_3.5.100.v20130422-1538.jar",
"third-party/java/eclipse/org.eclipse.jdt.core_3.9.2.v20140114-1555.jar",
"third-party/java/eclipse/org.eclipse.osgi_3.9.1.v20140110-1610.jar",
"third-party/java/gson/gson-2.2.4.jar",
"third-party/java/guava/guava-18.0.jar",
"third-party/java/guice/guice-3.0.jar",
"third-party/java/guice/guice-assistedinject-3.0.jar",
"third-party/java/guice/guice-multibindings-3.0.jar",
"third-party/java/icu4j/icu4j-54.1.1.jar",
"third-party/java/ini4j/ini4j-0.5.2.jar",
"third-party/java/jackson/jackson-annotations-2.0.5.jar",
"third-party/java/jackson/jackson-core-2.0.5.jar",
"third-party/java/jackson/jackson-databind-2.0.5.jar",
"third-party/java/jackson/jackson-datatype-jdk7-2.5.0.jar",
"third-party/java/jetty/jetty-all-9.0.4.v20130625.jar",
"third-party/java/jetty/servlet-api.jar",
"third-party/java/joda-time/joda-time-2.2.jar",
"third-party/java/jsr/javax.inject-1.jar",
"third-party/java/jsr/jsr305.jar",
"third-party/java/log4j/log4j-1.2.16.jar",
"third-party/java/nailgun/nailgun-server-0.9.2-SNAPSHOT.jar",
"third-party/java/slf4j/slf4j-api-1.7.2.jar",
"third-party/java/slf4j/slf4j-log4j12-1.7.2.jar",
"third-party/java/stringtemplate/ST-4.0.8.jar",
"third-party/java/thrift/libthrift-0.7.0.jar",
"third-party/java/xz-java-1.3/xz-1.3.jar",
]
RESOURCES = {
"abi_processor_classes": "build/abi_processor/classes",
"android_agent_path": "assets/android/agent.apk",
"buck_client": "build/ng",
"buck_server": "bin/buck",
"dx": "third-party/java/dx-from-kitkat/etc/dx",
"jacoco_agent_jar": "third-party/java/jacoco/jacocoagent.jar",
"log4j_config_file": "config/log4j.properties",
"logging_config_file": "config/logging.properties",
"path_to_asm_jar": "third-party/java/asm/asm-debug-all-5.0.3.jar",
"path_to_buck_py": "src/com/facebook/buck/parser/buck.py",
"path_to_compile_asset_catalogs_build_phase_sh": (
"src/com/facebook/buck/apple/compile_asset_catalogs_build_phase.sh"),
"path_to_compile_asset_catalogs_py": (
"src/com/facebook/buck/apple/compile_asset_catalogs.py"),
"path_to_intellij_py": "src/com/facebook/buck/command/intellij.py",
"path_to_pathlib_py": "third-party/py/pathlib/pathlib.py",
"path_to_pex": "src/com/facebook/buck/python/pex.py",
"path_to_python_test_main": "src/com/facebook/buck/python/__test_main__.py",
"path_to_sh_binary_template": "src/com/facebook/buck/shell/sh_binary_template",
"path_to_static_content": "webserver/static",
"quickstart_origin_dir": "src/com/facebook/buck/cli/quickstart/android",
"report_generator_jar": "build/report-generator.jar",
"testrunner_classes": "build/testrunner/classes",
}
class BuckRepo(BuckTool):
def __init__(self, buck_bin_dir, buck_project):
super(BuckRepo, self).__init__(buck_project)
self._buck_dir = self._platform_path(os.path.dirname(buck_bin_dir))
self._build_success_file = os.path.join(
self._buck_dir, "build", "successful-build")
dot_git = os.path.join(self._buck_dir, '.git')
self._is_git = os.path.exists(dot_git) and os.path.isdir(dot_git) and which('git') and \
sys.platform != 'cygwin'
self._is_buck_repo_dirty_override = os.environ.get('BUCK_REPOSITORY_DIRTY')
buck_version = buck_project.buck_version
if self._is_git and not buck_project.has_no_buck_check and buck_version:
revision = buck_version[0]
branch = buck_version[1] if len(buck_version) > 1 else None
self._checkout_and_clean(revision, branch)
self._build()
def _checkout_and_clean(self, revision, branch):
with Tracing('BuckRepo._checkout_and_clean'):
if not self._revision_exists(revision):
print(textwrap.dedent("""\
Required revision {0} is not
available in the local repository.
Buck is fetching updates from git. You can disable this by creating
a '.nobuckcheck' file in your repository, but this might lead to
strange bugs or build failures.""".format(revision)),
file=sys.stderr)
git_command = ['git', 'fetch']
git_command.extend(['--all'] if not branch else ['origin', branch])
try:
subprocess.check_call(
git_command,
stdout=sys.stderr,
cwd=self._buck_dir)
except subprocess.CalledProcessError:
raise BuckToolException(textwrap.dedent("""\
Failed to fetch Buck updates from git."""))
current_revision = self._get_git_revision()
if current_revision != revision:
print(textwrap.dedent("""\
Buck is at {0}, but should be {1}.
Buck is updating itself. To disable this, add a '.nobuckcheck'
file to your project root. In general, you should only disable
this if you are developing Buck.""".format(
current_revision, revision)),
file=sys.stderr)
try:
subprocess.check_call(
['git', 'checkout', '--quiet', revision],
cwd=self._buck_dir)
except subprocess.CalledProcessError:
raise BuckToolException(textwrap.dedent("""\
Failed to update Buck to revision {0}.""".format(revision)))
if os.path.exists(self._build_success_file):
os.remove(self._build_success_file)
ant = self._check_for_ant()
self._run_ant_clean(ant)
raise RestartBuck()
def _join_buck_dir(self, relative_path):
return os.path.join(self._buck_dir, *(relative_path.split('/')))
def _is_dirty(self):
if self._is_buck_repo_dirty_override:
return self._is_buck_repo_dirty_override == "1"
if not self._is_git:
return False
output = check_output(
['git', 'status', '--porcelain'],
cwd=self._buck_dir)
return bool(output.strip())
def _has_local_changes(self):
if not self._is_git:
return False
output = check_output(
['git', 'ls-files', '-m'],
cwd=self._buck_dir)
return bool(output.strip())
def _get_git_revision(self):
if not self._is_git:
return 'N/A'
output = check_output(
['git', 'rev-parse', 'HEAD', '--'],
cwd=self._buck_dir)
return output.splitlines()[0].strip()
def _get_git_commit_timestamp(self):
if self._is_buck_repo_dirty_override or not self._is_git:
return -1
return check_output(
['git', 'log', '--pretty=format:%ct', '-1', 'HEAD', '--'],
cwd=self._buck_dir).strip()
def _revision_exists(self, revision):
returncode = subprocess.call(
['git', 'cat-file', '-e', revision],
cwd=self._buck_dir)
return returncode == 0
def _check_for_ant(self):
ant = which('ant')
if not ant:
message = "You do not have ant on your $PATH. Cannot build Buck."
if sys.platform == "darwin":
message += "\nTry running 'brew install ant'."
raise BuckToolException(message)
return ant
def _print_ant_failure_and_exit(self, ant_log_path):
print(textwrap.dedent("""\
::: 'ant' failed in the buck repo at '{0}',
::: and 'buck' is not properly built. It will be unusable
::: until the error is corrected. You can check the logs
::: at {1} to figure out what broke.""".format(
self._buck_dir, ant_log_path)), file=sys.stderr)
if self._is_git:
raise BuckToolException(textwrap.dedent("""\
::: It is possible that running this command will fix it:
::: git -C "{0}" clean -xfd""".format(self._buck_dir)))
else:
raise BuckToolException(textwrap.dedent("""\
::: It is possible that running this command will fix it:
::: rm -rf "{0}"/build""".format(self._buck_dir)))
def _run_ant_clean(self, ant):
clean_log_path = os.path.join(self._buck_project.get_buck_out_log_dir(), 'ant-clean.log')
with open(clean_log_path, 'w') as clean_log:
exitcode = subprocess.call([ant, 'clean'], stdout=clean_log,
cwd=self._buck_dir)
if exitcode is not 0:
self._print_ant_failure_and_exit(clean_log_path)
def _run_ant(self, ant):
ant_log_path = os.path.join(self._buck_project.get_buck_out_log_dir(), 'ant.log')
with open(ant_log_path, 'w') as ant_log:
exitcode = subprocess.call([ant], stdout=ant_log,
cwd=self._buck_dir)
if exitcode is not 0:
self._print_ant_failure_and_exit(ant_log_path)
def _build(self):
with Tracing('BuckRepo._build'):
if not os.path.exists(self._build_success_file):
print(
"Buck does not appear to have been built -- building Buck!",
file=sys.stderr)
ant = self._check_for_ant()
self._run_ant_clean(ant)
self._run_ant(ant)
open(self._build_success_file, 'w').close()
def _has_resource(self, resource):
return True
def _get_resource(self, resource, exe=False):
return self._join_buck_dir(RESOURCES[resource.name])
def _get_buck_version_uid(self):
with Tracing('BuckRepo._get_buck_version_uid'):
# First try to get the "clean" buck version. If it succeeds,
# return it.
clean_version = get_clean_buck_version(
self._buck_dir,
allow_dirty=self._is_buck_repo_dirty_override == "1")
if clean_version is not None:
return clean_version
# Otherwise, if there is a .nobuckcheck file, or if there isn't
# a .buckversion file, fall back to a "dirty" version.
if (self._buck_project.has_no_buck_check or
not self._buck_project.buck_version):
return get_dirty_buck_version(self._buck_dir)
if self._has_local_changes():
print(textwrap.dedent("""\
::: Your buck directory has local modifications, and therefore
::: builds will not be able to use a distributed cache.
::: The following files must be either reverted or committed:"""),
file=sys.stderr)
subprocess.call(
['git', 'ls-files', '-m'],
stdout=sys.stderr,
cwd=self._buck_dir)
elif os.environ.get('BUCK_CLEAN_REPO_IF_DIRTY') != 'NO':
print(textwrap.dedent("""\
::: Your local buck directory is dirty, and therefore builds will
::: not be able to use a distributed cache."""), file=sys.stderr)
if sys.stdout.isatty():
print(
"::: Do you want to clean your buck directory? [y/N]",
file=sys.stderr)
choice = raw_input().lower()
if choice == "y":
subprocess.call(
['git', 'clean', '-fd'],
stdout=sys.stderr,
cwd=self._buck_dir)
raise RestartBuck()
return get_dirty_buck_version(self._buck_dir)
def _get_extra_java_args(self):
return [
"-Dbuck.git_commit={0}".format(self._get_git_revision()),
"-Dbuck.git_commit_timestamp={0}".format(
self._get_git_commit_timestamp()),
"-Dbuck.git_dirty={0}".format(int(self._is_dirty())),
]
def _get_bootstrap_classpath(self):
return self._join_buck_dir("build/bootstrapper/classes")
def _get_java_classpath(self):
return self._pathsep.join([self._join_buck_dir(p) for p in JAVA_CLASSPATHS])