@@ -561,25 +561,31 @@ defmodule Mix do
561561 * `:elixir` - if set, ensures the current Elixir version matches the given
562562 version requirement (Default: `nil`)
563563
564+ * `:system_env` (since v1.13.0) - a list or a map of system environment variable
565+ names with respective values as binaries. The system environment is made part
566+ of the `Mix.install/2` cache, so different configurations will lead to different apps
567+
564568 * `:config` (since v1.13.0) - a keyword list of keyword lists with application
565569 configuration to be set before the apps loaded. The configuration is part of
566570 the `Mix.install/2` cache, so different configurations will lead to different
567571 apps
568572
569- * `:system_env` (since v1.13.0) - a list or a map of system environment variable
570- names with respective values as binaries. The system environment is made part
571- of the `Mix.install/2` cache, so different configurations will lead to different apps
573+ * `:config_path` (since v1.14.0) - path to a configuration file. If a `runtime.exs`
574+ file exists in the same directory as the given path, it is loaded too.
575+
576+ * `:lockfile` (since v1.14.0) - path to a lockfile to be used as a basis of
577+ dependency resolution.
572578
573579 ## Examples
574580
575- To install `:decimal` and `:jason`:
581+ Installing `:decimal` and `:jason`:
576582
577583 Mix.install([
578584 :decimal,
579585 {:jason, "~> 1.0"}
580586 ])
581587
582- Using `:nx`, `:exla`, and configure the underlying applications
588+ Installing `:nx` & `:exla`, and configuring the underlying applications
583589 and environment variables:
584590
585591 Mix.install(
@@ -592,6 +598,23 @@ defmodule Mix do
592598 ]
593599 )
594600
601+ Installing a Mix project as a path dependency along with its configuration
602+ and deps:
603+
604+ # $ git clone https://github.com/hexpm/hexpm /tmp/hexpm
605+ # $ cd /tmp/hexpm && mix setup
606+
607+ Mix.install(
608+ [
609+ {:hexpm, path: "/tmp/hexpm", env: :dev},
610+ ],
611+ config_path: "/tmp/hexpm/config/config.exs",
612+ lockfile: "/tmp/hexpm/mix.lock"
613+ )
614+
615+ Hexpm.Repo.query!("SELECT COUNT(1) from packages")
616+ #=> ...
617+
595618 ## Limitations
596619
597620 There is one limitation to `Mix.install/2`, which is actually an Elixir
@@ -659,6 +682,7 @@ defmodule Mix do
659682 end )
660683
661684 config = Keyword . get ( opts , :config , [ ] )
685+ config_path = opts [ :config_path ] && Path . expand ( opts [ :config_path ] )
662686 system_env = Keyword . get ( opts , :system_env , [ ] )
663687 consolidate_protocols? = Keyword . get ( opts , :consolidate_protocols , true )
664688
@@ -675,18 +699,14 @@ defmodule Mix do
675699 Application . put_all_env ( config , persistent: true )
676700 System . put_env ( system_env )
677701
678- installs_root =
679- System . get_env ( "MIX_INSTALL_DIR" ) || Path . join ( Mix.Utils . mix_cache ( ) , "installs" )
680-
681- version = "elixir-#{ System . version ( ) } -erts-#{ :erlang . system_info ( :version ) } "
682- dir = Path . join ( [ installs_root , version , id ] )
702+ install_dir = install_dir ( id )
683703
684704 if opts [ :verbose ] do
685- Mix . shell ( ) . info ( "Mix.install/2 using #{ dir } " )
705+ Mix . shell ( ) . info ( "Mix.install/2 using #{ install_dir } " )
686706 end
687707
688708 if force? do
689- File . rm_rf! ( dir )
709+ File . rm_rf! ( install_dir )
690710 end
691711
692712 config = [
@@ -701,21 +721,51 @@ defmodule Mix do
701721 erlc_paths: [ "src" ] ,
702722 elixirc_paths: [ "lib" ] ,
703723 compilers: [ ] ,
704- consolidate_protocols: consolidate_protocols?
724+ consolidate_protocols: consolidate_protocols? ,
725+ config_path: config_path
705726 ]
706727
707728 started_apps = Application . started_applications ( )
708729 :ok = Mix.Local . append_archives ( )
709730 :ok = Mix.ProjectStack . push ( @ mix_install_project , config , "nofile" )
710- build_dir = Path . join ( dir , "_build" )
731+ build_dir = Path . join ( install_dir , "_build" )
732+ external_lockfile = opts [ :lockfile ] && Path . expand ( opts [ :lockfile ] )
711733
712734 try do
713- run_deps? = not File . dir? ( build_dir )
714- File . mkdir_p! ( dir )
735+ first_build? = not File . dir? ( build_dir )
736+ File . mkdir_p! ( install_dir )
737+
738+ File . cd! ( install_dir , fn ->
739+ if config_path do
740+ Mix.Task . rerun ( "loadconfig" )
741+ end
742+
743+ cond do
744+ external_lockfile ->
745+ md5_path = Path . join ( install_dir , "merge.lock.md5" )
746+
747+ old_md5 =
748+ case File . read ( md5_path ) do
749+ { :ok , data } -> Base . decode64! ( data )
750+ _ -> nil
751+ end
752+
753+ new_md5 = external_lockfile |> File . read! ( ) |> :erlang . md5 ( )
715754
716- File . cd! ( dir , fn ->
717- if run_deps? do
718- Mix.Task . rerun ( "deps.get" )
755+ if old_md5 != new_md5 do
756+ lockfile = Path . join ( install_dir , "mix.lock" )
757+ old_lock = Mix.Dep.Lock . read ( lockfile )
758+ new_lock = Mix.Dep.Lock . read ( external_lockfile )
759+ Mix.Dep.Lock . write ( lockfile , Map . merge ( old_lock , new_lock ) )
760+ File . write! ( md5_path , Base . encode64 ( new_md5 ) )
761+ Mix.Task . rerun ( "deps.get" )
762+ end
763+
764+ first_build? ->
765+ Mix.Task . rerun ( "deps.get" )
766+
767+ true ->
768+ :ok
719769 end
720770
721771 Mix.Task . rerun ( "deps.loadpaths" )
@@ -725,6 +775,10 @@ defmodule Mix do
725775 stop_apps ( Application . started_applications ( ) -- started_apps )
726776
727777 Mix.Task . rerun ( "compile" )
778+
779+ if config_path do
780+ Mix.Task . rerun ( "app.config" )
781+ end
728782 end )
729783
730784 for % { app: app , opts: opts } <- Mix.Dep . cached ( ) ,
@@ -746,6 +800,15 @@ defmodule Mix do
746800 end
747801 end
748802
803+ defp install_dir ( cache_id ) do
804+ install_root =
805+ System . get_env ( "MIX_INSTALL_DIR" ) ||
806+ Path . join ( Mix.Utils . mix_cache ( ) , "installs" )
807+
808+ version = "elixir-#{ System . version ( ) } -erts-#{ :erlang . system_info ( :version ) } "
809+ Path . join ( [ install_root , version , cache_id ] )
810+ end
811+
749812 @ doc """
750813 Returns whether `Mix.install/2` was called in the current node.
751814 """
0 commit comments