-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add a :make compiler #4134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Add a :make compiler #4134
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
eecd7b5
Add the first (draft-y) version of a Make compiler
whatyouhide c9f6687
Add support for :make_makefile in :make compiler
whatyouhide 81c578d
Take care of all OSs in the :make compiler
whatyouhide 55e6e9b
Use :cd in System.cmd/3 in the :make compiler
whatyouhide 93cc4b9
Clean up and refactor the :make compiler
whatyouhide File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
defmodule Mix.Tasks.Compile.Make do | ||
use Mix.Task | ||
|
||
@shortdoc "Runs `make` in the current project" | ||
|
||
@moduledoc """ | ||
Runs `make` in the current project. | ||
|
||
This task runs `make` in the current project; any output coming from `make` is | ||
printed in real-time on stdout. `make` will be called without specifying a | ||
Makefile, so there has to be a `Makefile` in the current working directory. | ||
|
||
## Configuration | ||
|
||
* `:make_executable` - it's a binary. It's the executable to use as the | ||
`make` program. By default, it's `"nmake"` on Windows, `"gmake"` on | ||
FreeBSD and OpenBSD, and `"make"` on everything else. | ||
|
||
* `:make_makefile` - it's a binary. It's the Makefile to use. Defaults to | ||
`"Makefile"` for Unix systems and `"Makefile.win"` for Windows systems. | ||
|
||
* `:make_targets` - it's a list of binaries. It's the list of Make targets | ||
that should be run. Defaults to `[]`, meaning `make` will run the first | ||
target. | ||
|
||
* `:make_cwd` - it's a binary. It's the directory where `make` will be run, | ||
relative to the root of the project. | ||
|
||
* `:make_error_message` - it's a binary. It's a custom error message that | ||
can be used to give instructions as of how to fix the error (e.g., it can | ||
be used to suggest installing `gcc` if you're compiling a C dependency). | ||
|
||
""" | ||
|
||
@spec run(OptionParser.argv) :: :ok | no_return | ||
def run(_args) do | ||
config = Mix.Project.config() | ||
build(config) | ||
Mix.Project.build_structure | ||
:ok | ||
end | ||
|
||
defp build(config) do | ||
exec = Keyword.get(config, :make_executable, executable_for_current_os()) | ||
makefile = Keyword.get(config, :make_makefile, :default) | ||
targets = Keyword.get(config, :make_targets, []) | ||
cwd = Keyword.get(config, :make_cwd, ".") | ||
error_msg = Keyword.get(config, :make_error_message, nil) | ||
|
||
args = args_for_makefile(exec, makefile) ++ targets | ||
|
||
case cmd(exec, args, cwd) do | ||
0 -> | ||
:ok | ||
exit_status -> | ||
raise_build_error(exec, exit_status, error_msg) | ||
end | ||
end | ||
|
||
# Runs `exec [args]` in `cwd` and prints the stdout and stderr in real time, | ||
# as soon as `exec` prints them (using `IO.Stream`). | ||
defp cmd(exec, args, cwd) do | ||
opts = [into: IO.stream(:stdio, :line), | ||
stderr_to_stdout: true, | ||
cd: cwd] | ||
|
||
{%IO.Stream{}, status} = System.cmd(executable(exec), args, opts) | ||
status | ||
end | ||
|
||
defp executable(exec) do | ||
System.find_executable(exec) || raise_executable_not_found(exec) | ||
end | ||
|
||
defp raise_executable_not_found(exec) do | ||
Mix.raise "`#{exec}` not found in the current path" | ||
end | ||
|
||
defp raise_build_error(exec, exit_status, error_msg) do | ||
msg = "Could not compile with `#{exec}`" | ||
|
||
if error_msg do | ||
msg = msg <> ".\n" <> error_msg | ||
end | ||
|
||
Mix.raise msg | ||
end | ||
|
||
defp executable_for_current_os() do | ||
case :os.type() do | ||
{:win32, _} -> "nmake" | ||
{:unix, type} when type in [:freebsd, :openbsd] -> "gmake" | ||
_ -> "make" | ||
end | ||
end | ||
|
||
# Returns a list of command-line args to pass to make (or nmake/gmake) in | ||
# order to specify the makefile to use. | ||
defp args_for_makefile("nmake", :default), do: ["/F", "Makefile.win"] | ||
defp args_for_makefile("nmake", makefile), do: ["/F", makefile] | ||
defp args_for_makefile(_, :default), do: [] | ||
defp args_for_makefile(_, makefile), do: ["-f", makefile] | ||
end |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
Code.require_file "../../test_helper.exs", __DIR__ | ||
|
||
defmodule Mix.Tasks.Compile.MakeTest do | ||
use MixTest.Case | ||
import ExUnit.CaptureIO | ||
|
||
setup do | ||
Mix.Project.push MixTest.Case.Sample | ||
:ok | ||
end | ||
|
||
test "running with a specific executable" do | ||
in_fixture "compile_make", fn -> | ||
with_project_config [make_executable: "nonexistentmake"], fn -> | ||
assert_raise Mix.Error, "`nonexistentmake` not found in the current path", fn -> | ||
run() | ||
end | ||
end | ||
end | ||
end | ||
|
||
test "running without a makefile" do | ||
msg = ~r/\ACould not compile with/ | ||
|
||
in_fixture "compile_make", fn -> | ||
File.rm_rf!("Makefile") | ||
|
||
capture_io fn -> | ||
assert_raise Mix.Error, msg, fn -> run() end | ||
end | ||
end | ||
end | ||
|
||
test "running with a makefile" do | ||
in_fixture "compile_make", fn -> | ||
File.write! "Makefile", """ | ||
target: | ||
\t@echo "hello" | ||
""" | ||
|
||
assert capture_io(fn -> run() end) =~ "hello\n" | ||
end | ||
end | ||
|
||
test "specifying targets" do | ||
in_fixture "compile_make", fn -> | ||
File.write! "Makefile", """ | ||
useless_target: | ||
\t@echo "nope" | ||
target: | ||
\t@echo "target" | ||
other_target: | ||
\t@echo "other target" | ||
""" | ||
|
||
with_project_config [make_targets: ~w(target other_target)], fn -> | ||
output = capture_io(fn -> run() end) | ||
assert output =~ "target\n" | ||
assert output =~ "other target\n" | ||
refute output =~ "nope" | ||
end | ||
end | ||
end | ||
|
||
test "specifying a cwd" do | ||
in_fixture "compile_make", fn -> | ||
File.mkdir_p!("subdir") | ||
File.write! "subdir/Makefile", """ | ||
all: | ||
\t@echo "subdir" | ||
""" | ||
|
||
with_project_config [make_cwd: "subdir"], fn -> | ||
assert capture_io(fn -> run() end) == "subdir\n" | ||
end | ||
end | ||
end | ||
|
||
test "specifying a makefile" do | ||
in_fixture "compile_make", fn -> | ||
File.write "MyMakefile", """ | ||
all: | ||
\t@echo "my makefile" | ||
""" | ||
|
||
with_project_config [make_makefile: "MyMakefile"], fn -> | ||
assert capture_io(fn -> run() end) == "my makefile\n" | ||
end | ||
end | ||
end | ||
|
||
test "specifying a custom error message" do | ||
in_fixture "compile_make", fn -> | ||
with_project_config [make_error_message: "try harder"], fn -> | ||
capture_io fn -> | ||
assert_raise Mix.Error, ~r/try harder/, fn -> run() end | ||
end | ||
end | ||
end | ||
end | ||
|
||
defp with_project_config(config, fun) do | ||
Mix.Project.in_project(:sample, ".", config, fn(_) -> fun.() end) | ||
end | ||
|
||
defp run(args \\ []) do | ||
Mix.Tasks.Compile.Make.run(args) | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also expose
make_executable
? The default is the OS lookup we perform. :)