It's a good question, and there are multiple answers.
The general name for the process is "bootstrapping" and it usually starts with a subset of the language, with just enough in it to write a compiler for that subset.
Suppose you have language X to implement, and you'd like to use language X to write the compiler. Define that subset and call it X0.
The first step is the hardest. You could write a compiler (or interpreter) for X0 in assembler, and then rewrite that compiler for X0 in the X0 language itself. The assembler-based compiler (when debugged) is used to compile the X0 compiler written X0. When that is debugged, you have first version of an X compiler written in X.
That's also one "boostrapping step". Next, extend the X0 compiler, still using the X0 subset language, to support an improved version of the language (X1, say). One important test will be to verify that both X0 and X1 compile the source to X1 and produce equivalent results. Now you have an X1 compiler and can use X1 features for future refinements.
That's another "bootstrapping step", and you can continue in the same way to get the full language implemented.
Assembly is not the only option for the first step. Some languages have been "hand compiled" from the first source version. This was done (I think) with McKeeman/Horning/Wortman's XPL programming language, used to teach compiler construction techniques back when I was learning this stuff. (_A Compiler Generator_ was the text. See: http://www.cs.toronto.edu/XPL/)
Moving to another machine and/or OS can be done as a series of bootstrap steps, too. Usually, only the code generation modules are affected by this--at least at first. Write a modified version of X that produces code for the other CPU/OS. When that is debugged and produces programs that run on the other system, you can use that to compile source for the new compiler into a version of the compiler that will RUN on the new system,